package com.qianxin.ids.idam.am.authn.factors.cass.upsso.refreshToken;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.gitee.starblues.realize.PluginUtils;
import com.qianxin.ids.idam.am.authn.api.context.IContextService;
import com.qianxin.ids.idam.am.authn.api.factors.AuthnFactorInstance;
import com.qianxin.ids.idam.am.authn.api.utils.AuthnFactorInstanceUtils;
import com.qianxin.ids.idam.am.authn.factors.cass.upsso.api.CassCommon;
import com.qianxin.ids.idam.am.authn.factors.cass.upsso.api.CassUpSsoProperties;
import com.qianxin.ids.idam.am.session.api.entity.AuthnContext;
import com.qianxin.ids.idam.am.session.api.entity.UserSession;
import com.qianxin.ids.idam.am.session.api.entity.UserSessionQueryParams;
import com.qianxin.ids.idam.am.session.api.iface.ISessionService;
import com.qianxin.ids.idam.bizcomps.common.iface.cluster.IClusterService;
import com.qianxin.ids.idam.framework.common.log.FilterKeyWord;
import com.qianxin.ids.idam.framework.common.utils.ServerModeHelper;
import com.qianxin.ids.idam.framework.common.utils.ServerSide;
import com.qianxin.ids.idam.framework.timer.common.TimerTask;
import com.qianxin.ids.idam.sdk.context.SpringBeanFactory;
import com.qianxin.ids.idam.sdk.entity.Pair;
import com.qianxin.ids.idam.sdk.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.*;

/**
 * @author xiongjianlin
 */
@Slf4j
@Component
public class RefreshTokenTask implements TimerTask, Serializable {

    @Autowired
    RefreshTokenCache cache;
    @Autowired
    private PluginUtils pluginUtils;

    private IContextService contextService;
    private ISessionService sessionService;
    private ServerModeHelper serverModeHelper;
    private IClusterService clusterService;
    private FilterKeyWord filterKeyWord;
    @PostConstruct
    private void init() {
        contextService = pluginUtils.getMainBean(IContextService.class);
        sessionService = pluginUtils.getMainBean(ISessionService.class);
        serverModeHelper = pluginUtils.getMainBean(ServerModeHelper.class);
        clusterService = SpringBeanFactory.getBean("clusterService");
        filterKeyWord = pluginUtils.getMainBean(FilterKeyWord.class);
    }

    @Override
    public void run() {
        if (serverModeHelper != null && clusterService != null && (serverModeHelper.getServerSide() != ServerSide.RUNTIME || !clusterService.isMaster())) {
            log.info("RefreshTokenTask ==> only runtime and master service can execute RefreshTokenTask, current is[{},{}], ignore it.", serverModeHelper.getServerSide(), clusterService.isMaster());
            return;
        }
        log.info("RefreshTokenTask ==> 定时任务开始执行");
        ConcurrentUniquePriorityQueue expiryQueue = cache.getPriorityQueue();
        // 1. 从队列中取出需要刷新的条目
        List<RefreshEntity> toRefresh = new ArrayList<>();
        while (!expiryQueue.isEmpty()) {
            RefreshEntity entity = expiryQueue.peek();
            if (!entity.shouldRefresh()) {
                break; // 后续条目未到刷新时间
            }
            // 校验是否在admin侧被下线了
            if (StringUtils.isNotEmpty(cache.popAdminLogOutCache(entity.getContextId()))){
                log.info("RefreshTokenTask ==> 用户在admin侧被下线，跳过续期 。 【{}】", entity);
                // 取出多余会话
                expiryQueue.poll();
                break;
            }
            toRefresh.add(expiryQueue.poll());
        }

        // 2. 批量处理
        if (!toRefresh.isEmpty()) {
            String instanceId = toRefresh.get(0).getInstanceId();
            if (StringUtils.isEmpty(instanceId)) {
                log.info("RefreshTokenTask ==> 缓存获取认证id失败");
                return;
            }
            AuthnFactorInstance instance = AuthnFactorInstanceUtils.getFactorInstance(instanceId);
            CassUpSsoProperties properties = CassUpSsoProperties.parse(instance);
            // 重新更新缓存、入队。
            toRefresh.forEach(entity -> refreshTokenAndUpdateCache(entity, properties));
        }


    }


    /**
     *  刷新 Token 并更新缓存
      */
    private void refreshTokenAndUpdateCache(RefreshEntity refreshEntity, CassUpSsoProperties properties) {
        try {
            log.info("RefreshTokenTask ==> 刷新用户【{}】 Token,  缓存信息={}", refreshEntity.getUserCode(), refreshEntity);
            // 本次场景不会返回新token。
            boolean success = callRefreshTokenApi(refreshEntity, properties);
            if (success){
                refreshEntity.updateToken(refreshEntity.getToken() , properties.getTokenExpireTime());
                log.info("RefreshTokenTask ==> 用户【{}】 Token下次过期时间为 : {}", refreshEntity.getUserCode(), new Date(refreshEntity.getExpireTime()));
            }
            // 无论成功与否都要入库。 TODO 如果一直需求失败是否需要后续处理？？
            cache.put(refreshEntity);
        } catch (Exception e) {
            log.info("RefreshTokenTask ==> 刷新 Token 失败: 缓存信息={}", refreshEntity, e);
        }
    }

    /**
     * 实际调用刷新token接口。
     * @param entity  缓存的相关信息
     * @param properties  认证源信息
     * @return  新的token或者其他对象。
     */
    private boolean callRefreshTokenApi(RefreshEntity entity, CassUpSsoProperties properties){
        HttpResponse response = HttpRequest.get(properties.getRefreshTokenUrl())
                .header("Accept", "application/json, text/plain, */*")
                .header("X-CSRF-TOKEN", entity.getToken())
                .cookie("SESSION="+ entity.getSession())
                .timeout(5000)
                .execute();
        // 3. 打印响应结果
        log.info("RefreshTokenTask ==> HTTP状态码: [{}]" ,response.getStatus());
        if (filterKeyWord.isEnable(this.getClass().getName())) {
            log.info("RefreshTokenTask ==> 响应内容: [{}]" , response.body());
        }
        JSONObject json = JSON.parseObject(response.body());
        if (json.containsKey("status")){
            if (json.getInteger("status")!= 0){
                log.info("RefreshTokenTask ==> 续期失败:  [{}]" ,entity);
                return false;
            }
        }
        return true;
    }

    @Override
    public String getTaskName() {
        return "RefreshTokenTask";
    }

    /**
     * 定时执行时间  : TODO 监听认证源配置变更，以动态修改定时任务间隔。
     */
    public Long getTime() {
        // 一分钟
        return 60L * 2;
    }


    /**
     * 重载数据库会话到缓存。
     *  整个作为一个异步进行处理。
     */
    public void reload() {
        Executor executor = Executors.newFixedThreadPool(2);
        // 1. 先执行缓存初始化
         cache.cacheInit()
                .thenApply(initialized -> {
                    // 2. 初始化完成后执行数据加载
                    if (initialized) {
                        // 加载数据
                        return loadData();
                    } else {
                        throw new IllegalStateException("RefreshTokenTask ==>缓存初始化失败，无法加载数据");
                    }
                })
                .thenAcceptAsync(all -> {
                    // 3. 异步保存到缓存
                    cache.putAll(all);
                    log.info("RefreshTokenTask ==>数据已异步加载到缓存");
                }, executor)
                .exceptionally(ex -> {
                    log.info("RefreshTokenTask ==>异步加载数据失败", ex);
                    return null;
                });
    }


    public List<RefreshEntity> loadData(){
        UserSessionQueryParams params = new UserSessionQueryParams();
        params.setPageSize(99999);
        Pair<Long, List<UserSession>> pair = sessionService.queryUserSessionFromDB(params);
        log.info("RefreshTokenTask ==>online user total size is {}", pair.getFst());
        // 获取所有会话上下文
        List<RefreshEntity> all = Collections.synchronizedList(new ArrayList<>(pair.getSnd().size()));
        pair.getSnd().parallelStream().forEach(e -> {
            AuthnContext authnContext = contextService.reductionContext(e, null);
            if (StringUtils.isNotEmpty(authnContext.getAttribute(CassCommon.SESSION))){
                RefreshEntity entity = new RefreshEntity(authnContext);
                all.add(entity);
            }
        });
        return all;
    }

    public void clear() {
        cache.destroy();
    }


}
